package com.wei.czz.framework.admin.helper;

import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.constant.EntityConstant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.enums.admin.user.RequestChannelEnum;
import com.wei.czz.common.enums.common.dict.DictEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.po.RolePo;
import com.wei.czz.common.po.UserPo;
import com.wei.czz.common.po.admin.accessToken.UserAccessTokenPo;
import com.wei.czz.common.utils.CacheKeyUtils;
import com.wei.czz.common.utils.CopyUtils;
import com.wei.czz.common.utils.RandomNumUtils;
import com.wei.czz.common.utils.SecurityUtils;
import com.wei.czz.framework.admin.entity.UserAccessTokenEntity;
import com.wei.czz.framework.admin.entity.UserEntity;
import com.wei.czz.framework.admin.entity.UserLoginEntity;
import com.wei.czz.framework.admin.service.UserAccessTokenService;
import com.wei.czz.framework.admin.service.UserLoginService;
import com.wei.czz.framework.admin.service.UserRoleService;
import com.wei.czz.framework.admin.service.UserService;
import com.wei.czz.framework.common.entity.DictEntity;
import com.wei.czz.framework.common.handler.ThrottleHandler;
import com.wei.czz.framework.common.handler.redis.RedisHashHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import com.wei.czz.framework.common.wrapper.DictWrapper;
import com.wei.czz.framework.security.entity.SecurityUser;
import lombok.AllArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2024-06-02 10:16:40
 * className: UserHelper 用户帮助类
 * version: 1.0
 * description:
 */
@Component
@AllArgsConstructor
public class UserHelper {

    private static final Logger log = LoggerFactory.getLogger(UserHelper.class);

    private final UserService userService;

    private final UserRoleService userRoleService;

    private final UserLoginService userLoginService;

    private final UserAccessTokenService userAccessTokenService;

    private final RedisHandler redisHandler;

    private final RedisHashHandler redisHashHandler;

    private final RedisStringHandler redisStringHandler;

    private final ThrottleHandler throttleHandler;

    private final RedissonClient redissonClient;

    private final DictWrapper dictWrapper;

    /**
     * 判断请求用户是否超级管理员
     *
     * @return true-是 false-不是
     */
    public boolean isSuperManager() {
        return SecurityUtils.securityUserOptional()
                .map(securityUser -> Boolean.TRUE.equals(securityUser.getSuperManager()))
                .orElse(Boolean.FALSE);
    }

    /**
     * 判断用户是否超级管理员
     *
     * @return true-是 false-不是
     */
    public boolean isSuperManager(Long userId) {
        /*
         * 1、查询Redis缓存，获取用户是否超级管理员
         * 2、如果Redis缓存中没有，则查询数据库，获取用户是否超级管理员
         * 3、如果数据库中没有，则返回false
         */
        String redisKey = RedisConstant.STRING + EntityConstant.SUPER_MANAGER + RedisConstant.SPLIT+ userId;
        Integer value = redisStringHandler.get(redisKey);
        if (CommonEnum.ZERO.getValue().equals(value)) {
            log.info("【缓存】用户是否超级管理员。userId={}", userId);
            // 节流执行
            throttleHandler.throttleRun(() -> {
                // 尝试更新缓存键的过期时间
                redisHandler.tryUpdateKeyExpire(redisKey, 900, RandomNumUtils.randomNum(1800, 10));
            }, redisKey, 1500, TimeUnit.MILLISECONDS);
            return true;
        }

        String lockKey = RedisConstant.LOCK + redisKey;
        RLock lock = redissonClient.getLock(lockKey);
        // 尝试加锁
        boolean bool = lock.tryLock();
        log.info("判断用户是否超级管理员，尝试加锁完成。lockKey={} bool={}", lockKey, bool);
        if (!bool) {
            // 加锁
            lock.lock();
            log.info("判断用户是否超级管理员，加锁成功");
        }

        try {
            if (!bool) {
                // double check
                value = redisStringHandler.get(redisKey);
                if (CommonEnum.ZERO.getValue().equals(value)) {
                    log.info("【double check 缓存】用户是否超级管理员。userId={}", userId);
                    return true;
                }
            }

            String id = userId.toString();

            /*
                获取用户是否超级管理员
             */
            List<DictEntity> dictList = dictWrapper.getDictList(DictEnum.SYSTEM_USER);
            // 判断用户是否超级管理员
            boolean isSuperManager = dictList.stream().anyMatch(dict -> dict.getValue().equals(id));
            log.info("判断用户是否超级管理员完成。userId={} isSuperManager={}", userId, isSuperManager);

            // 缓存（30分钟）
            redisStringHandler.set(redisKey, isSuperManager ? CommonEnum.ZERO.getValue() : CommonEnum.ONE.getValue(),
                    30, TimeUnit.MINUTES);

            return isSuperManager;
        } catch (Exception e) {
            log.error("判断请求用户是否超级管理员异常。userId={} message={}", userId, e.getMessage(), e);
            return false;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    /**
     * 填充用户编号
     *
     * @param userNo 用户编号
     * @return 用户编号
     */
    public String fullUserNo(Long userNo) {

        String _userNo = userNo.toString();
        int length = _userNo.length();
        if (length > 4) {
            return _userNo;
        }

        return "0".repeat(4 - length) + userNo;
    }

    /**
     * 获取用户
     *
     * @param userId 用户主键
     * @return 用户对象
     */
    public UserEntity get(Long userId) {

        // 查询Redis缓存，从用户缓存中获取用户信息
        String hashKey = CacheKeyUtils.getRedisHashKey(EntityConstant.USER);
        String valueKey = userId.toString();
        UserEntity user = redisHashHandler.get(hashKey, valueKey);
        if (Objects.nonNull(user)) {
            log.info("从redis缓存获取用户。user={}", user);
            return user;
        }

        /*

         */
        String lockKey = RedisConstant.LOCK + hashKey + Constant.SPLIT + valueKey;
        RLock lock = redissonClient.getLock(lockKey);
        // 尝试加锁
        boolean bool = lock.tryLock();
        log.info("获取用户，尝试加锁完成。lockKey={} bool={}", lockKey, bool);
        if (!bool) {
            // 加锁
            lock.lock();
            log.info("获取用户，加锁成功");
        }
        try {
            if (!bool) {
                // double check
                user = redisHashHandler.get(hashKey, valueKey);
                if (user!= null) {
                    log.info("double check 从redis缓存获取用户。user={}", user);
                    return user;
                }
            }

            /*
                查询数据库，获取用户
             */
            user = userService.get(userId);
            // 主动清理密码
            user.setPassword(null);

            // 缓存用户
            redisHashHandler.put(hashKey, valueKey, user);

            return user;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 获取用户名称操作
     *
     * @param userId 用户主键
     * @return 用户名称
     */
    public String getUsername(Long userId) {
        /*
            获取用户
         */
        UserEntity user = this.get(userId);

        return user.getUsername();
    }

    /**
     * 获取用户登录信息
     *
     * @param userId             用户主键
     * @param requestChannelEnum 请求渠道枚举对象
     * @return 用户登录信息
     */
    public SecurityUser getSecurityUser(long userId, RequestChannelEnum requestChannelEnum) {
        //
        String redisKey = CacheKeyUtils.getSecurityUserKey(userId);
        // 查询Redis缓存，获取用户登录数据
        SecurityUser securityUser = redisStringHandler.get(redisKey);
        if (Objects.nonNull(securityUser)) {
            // 节流执行
            throttleHandler.throttleRun(() -> {
                // 尝试更新过期时间
                redisHandler.tryUpdateKeyExpire(redisKey, 300, RandomNumUtils.randomNum(600, 10));
            }, redisKey, 1, TimeUnit.MINUTES);

            return securityUser;
        }

        //
        String lockKey = RedisConstant.LOCK + redisKey;
        RLock lock = redissonClient.getLock(lockKey);
        // 尝试加锁
        boolean bool = lock.tryLock();
        log.info("获取用户登录对象，尝试加锁完成。lockKey={} bool={}", lockKey, bool);
        if (!bool) {
            lock.lock();
            log.info("获取用户登录对象，加锁成功");
        }

        try {

            if (!bool) {
                // 重复查询Redis缓存，获取用户登录数据
                securityUser = redisStringHandler.get(redisKey);
                if (Objects.nonNull(securityUser)) {
                    log.debug("double check 从缓存中获取用户登录对象：{}", securityUser);
                }

                return securityUser;
            }

            // 获取用户登录记录
            List<UserLoginEntity> userLoginList = userLoginService.findList(userId, requestChannelEnum.getValue());
            if (userLoginList.isEmpty()) {
                log.info("用户登录记录丢失");
                throw new CzzException(ResultEnum.ILLEGAL_FAIL);
            }
            // 最新一条用户登录记录
            UserLoginEntity userLogin = userLoginList.get(userLoginList.size() - 1);
            if (!CommonEnum.ZERO.getValue().equals(userLogin.getStatus())) {
                log.info("用户不是登录状态：status={}", userLogin.getStatus());

                String message = CommonEnum.ONE.getValue().equals(userLogin.getStatus()) ?
                        "您已退出登录，请求被拒绝" : "您已被踢出登录，请求被拒绝";
                throw new CzzException(ResultEnum.SERVICE_FAIL.getCode(), message);
            }

            /*
                获取用户数据
             */
            UserEntity user = this.get(userId);

            /*
                获取用户令牌
             */
            List<UserAccessTokenEntity> userAccessTokenList = userAccessTokenService.findEffectList(userId, null);
            if (userAccessTokenList.isEmpty()) {
                log.info("用户令牌已过期");
                throw new CzzException(ResultEnum.TOKEN_EXPIRE.getCode(), "登录已过期，请重新登陆后再操作");
            }
            // list映射
            List<UserAccessTokenPo> accessTokenList = CopyUtils.mapList(userAccessTokenList, userAccessToken -> {
                UserAccessTokenPo userAccessTokenPo = new UserAccessTokenPo();
                userAccessTokenPo.setChannel(userAccessToken.getChannel())
                        .setAccessToken(userAccessToken.getAccessToken())
                        .setAccessExpireTime(userAccessToken.getAccessExpireTime())
                        .setRefreshToken(userAccessToken.getRefreshToken())
                        .setRefreshExpireTime(userAccessToken.getRefreshExpireTime());
                return userAccessTokenPo;
            });

            // 最新一条用户令牌
            UserAccessTokenPo accessTokenPo = accessTokenList.get(accessTokenList.size() - 1);

            // 属性封装
            UserPo userPo = new UserPo();
            userPo.setUserId(userId)
                    .setUsername(user.getUsername())
                    .setAvatar(user.getAvatar())
                    .setPhone(user.getPhone())
                    .setEmail(user.getEmail())
                    .setLoginName(user.getLoginName())
                    .setStatus(user.getStatus());

            // 判断用户是否超级管理员
            boolean superManager = this.isSuperManager(userId);

            Set<String> roleKeySet = new HashSet<>();
            Set<String> permitSet = new HashSet<>();
            if (!superManager) {
                // 不是超级管理员，查询拥有的操作权限
                List<RolePo> rolePoList = userRoleService.findUserPermitList(userId);
                for (RolePo rolePo : rolePoList) {
                    roleKeySet.add(rolePo.getRoleKey());
                    permitSet.add(rolePo.getPermit());
                }
            }

            securityUser = new SecurityUser();
            // 设置用户个人信息、用户令牌、过期时间戳和用户权限列表属性值
            securityUser.setUserPo(userPo)
                    .setAccessToken(accessTokenPo)
                    .setAccessTokenList(accessTokenList)
                    .setRoleKeySet(roleKeySet)
                    .setPermitSet(permitSet)
                    .setSuperManager(superManager);

            // 操作Redis缓存，保存用户登录信息
            redisStringHandler.set(redisKey, securityUser, RandomNumUtils.randomNum(600, 10),
                    TimeUnit.SECONDS);

            return securityUser;

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }
}
